package net.gdface.utils;

import static com.google.common.base.Preconditions.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ForwardingMap;

/**
 * 反射工具类
 * @author guyadong
 *
 */
public class ReflectionUtils {
	public static final String PROP_CLASSNAME = "className";
	public static final String PROP_STATICMETHODNAME = "staticMethodName";
	public static final String PROP_PARAMETERTYPES = "parameterTypes";
	public static final String PROP_CONSTRUCTORARGS = "constructorArgs";
	public static final String PROP_CONSTRUCTORPARAMS = "constructorParams";
	private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
	private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
	private static final Set<String> overridableObjectMethods = new HashSet<>(Arrays.asList("clone","finalize","hashCode","equals","toString"));
	public ReflectionUtils() {
	}
	private static class ParameterMap extends ForwardingMap<String,Object>{
		private final Map<String, Object> delegate;
		public ParameterMap(Map<String, Object> delegate) {
			super();
			if(null == delegate){
				this.delegate = Collections.emptyMap();
			}else{
				this.delegate = delegate;
			}
		}
		@Override
		protected Map<String, Object> delegate() {
			return delegate;
		}
		@SuppressWarnings("unchecked")
		public final <T> T of(String key,T defaultValue){
			checkArgument(null != key);
			Object value = delegate.get(key);
			try{
				return null == value ? defaultValue: (T) value;
			}catch(ClassCastException e){
				throw new IllegalArgumentException("invalid parameter: " + key + ",caused by " + e.getMessage());
			}
		}
		public final <T> T of(String key){
			return of(key,null);
		}
	}
	@SuppressWarnings("unchecked")
	public static <T> Class<? extends T> getInstanceClass(Class<T> superClass,String instanceClassName) 
			throws ClassNotFoundException{
		checkArgument(null != superClass && !Strings.isNullOrEmpty(instanceClassName));
		Class<?> instanceClass = (Class<?>) Class.forName(instanceClassName);
		checkInstanceClass(superClass,instanceClass);
		return  (Class<? extends T>)instanceClass;
	}
	public static <T> T getInstance(Class<T> superClass,Map<String,Object> params) 
			throws NoSuchMethodException, ClassNotFoundException{
		ParameterMap paramMap = new ParameterMap(params);
		String clazzName = paramMap.of(PROP_CLASSNAME);
		Class<? extends T> instanceClass = getInstanceClass(superClass,clazzName);
		String staticMethodName =paramMap.of(PROP_STATICMETHODNAME);
		if(null != staticMethodName){
			try{
				return getInstanceByStaticMethod(superClass,instanceClass,staticMethodName);
			}catch(NoSuchMethodException e){
				// 找不到静态方法则尝试用构造方法创建实例
			}
		}
		if(paramMap.containsKey(PROP_CONSTRUCTORPARAMS)){
			LinkedHashMap<Class<?>,Object> constructorParams = paramMap.of(PROP_CONSTRUCTORPARAMS);
			return getInstanceByConstructor(superClass,instanceClass,constructorParams);
		}else{
			Class<?>[] parameterTypes = paramMap.of(PROP_PARAMETERTYPES);
			Object[] ctorArgs = paramMap.of(PROP_CONSTRUCTORARGS);
			return getInstanceByConstructor(superClass,instanceClass,parameterTypes,ctorArgs);
		}
	}
	private static void checkInstanceClass(Class<?> superClass,Class<?> instanceClass){
		checkArgument(null != superClass && null != instanceClass);
		checkArgument(!instanceClass.isInterface() && superClass.isAssignableFrom(instanceClass),
				"%s not a implemenation of %s",instanceClass.getName(),superClass.getSimpleName());
		checkArgument(!Modifier.isAbstract(instanceClass.getModifiers()),
				"%s is abstract class",instanceClass.getName());
		checkArgument(Modifier.isStatic(instanceClass.getModifiers()) || null == instanceClass.getDeclaringClass(),
				"%s is not static class",instanceClass.getName());
	}
	public static <T> T getInstanceByConstructor(Class<T> superClass,Class<? extends T> instanceClass,Class<?>[] parameterTypes,Object[] constructorArgs) 
			throws NoSuchMethodException{
		checkInstanceClass(superClass,instanceClass);
		parameterTypes = MoreObjects.firstNonNull(parameterTypes, EMPTY_CLASS_ARRAY);
		constructorArgs = MoreObjects.firstNonNull(constructorArgs, EMPTY_OBJECT_ARRAY);
		checkArgument(parameterTypes.length == constructorArgs.length);
		try{
			Constructor<? extends T> ctor = instanceClass.getConstructor(parameterTypes);
			try {
				return ctor.newInstance(constructorArgs);
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		} finally{}
	}
	public static <T> T getInstanceByConstructor(Class<T> superClass,Class<? extends T> instanceClass,LinkedHashMap<Class<?>,Object> constructorParams) 
			throws NoSuchMethodException{
		checkInstanceClass(superClass,instanceClass);
		constructorParams = MoreObjects.firstNonNull(constructorParams, new LinkedHashMap<Class<?>,Object>());
		Class<?>[] parameterTypes = constructorParams.keySet().toArray(new Class<?>[constructorParams.size()]);
		Object[] initargs = constructorParams.values().toArray(new Object[constructorParams.size()]);
		Constructor<? extends T> ctor = instanceClass.getConstructor(parameterTypes);
		try {
			return ctor.newInstance(initargs);
		} catch (InvocationTargetException e) {
			Throwables.throwIfUnchecked(e.getTargetException());
			throw new RuntimeException(e.getTargetException());
		}catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
	}
	@SuppressWarnings("unchecked")
	public static <T> T getInstanceByStaticMethod(Class<T> superClass,Class<? extends T> instanceClass,String staticMethodName) 
			throws NoSuchMethodException{
		checkArgument(!Strings.isNullOrEmpty(staticMethodName));
		checkInstanceClass(superClass,instanceClass);
		try {
			Method method = instanceClass.getMethod(staticMethodName);
			checkArgument(Modifier.isStatic(method.getModifiers()),"%s is not a static method",method.toString());
			checkArgument(superClass.isAssignableFrom(method.getReturnType()),"unexpect return type %s",method.getReturnType().toString());
			try{
				return (T) method.invoke(null);
			}catch(ClassCastException e){
				throw new IllegalArgumentException(
						String.format("invalid return type of static method %s caused by %s",method.toString(),e.getMessage()));
			}
		} catch(NoSuchMethodException e){
			throw e;
		}catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
	}
	/**
	 * 反射获取{@code object}的私有成员
	 * @param object
	 * @param name
	 * @return 成员对象
	 */
	@SuppressWarnings("unchecked")
	public static <T> T valueOfField(Object object,String name){
		try {
			Field field = checkNotNull(object,"object is null").getClass().getDeclaredField(checkNotNull(name,"name is null"));
			field.setAccessible(true);
			return (T) field.get(object);
		} catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
	}
	/**
	 * 反射修改{@code object}的私有成员
	 * @param object 对象
	 * @param name 成员名
	 * @param value 成员值
	 */
	public static void setValueOfField(Object object,String name,Object value){
		try {
			Field field = checkNotNull(object,"object is null").getClass().getDeclaredField(checkNotNull(name,"name is null"));
			field.setAccessible(true);
			boolean isfinal = (field.getModifiers() & Modifier.FINAL) == Modifier.FINAL;
			String modifiersName = "modifiers";
			if(isfinal){
				Field modifiers = field.getClass().getDeclaredField(modifiersName);
				// 去除final 修饰符
				modifiers.setAccessible(true);
				modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);				
			}
			field.set(object,value);
			if(isfinal){
				Field modifiers = field.getClass().getDeclaredField(modifiersName);
				// 恢复final 修饰符
				modifiers.setInt(field, field.getModifiers() | Modifier.FINAL);
			}
		} catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
	}
	/**
	 * 反射修改{@code clazz}的静态私有成员
	 * @param clazz 类型
	 * @param name 成员名
	 * @param value 成员值
	 */
	public static void setValueOfStaticField(Class<?> clazz,String name,Object value){
		try {
			Field field = checkNotNull(clazz,"clazz is null").getDeclaredField(checkNotNull(name,"name is null"));
			field.setAccessible(true);
			boolean isfinal = (field.getModifiers() & Modifier.FINAL) == Modifier.FINAL;
			String modifiersName = "modifiers";
			if(isfinal){
				Field modifiers = field.getClass().getDeclaredField(modifiersName);
				// 去除final 修饰符
				modifiers.setAccessible(true);
				modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);				
			}
			field.set(clazz,value);
			if(isfinal){
				// 恢复final 修饰符
				Field modifiers = field.getClass().getDeclaredField(modifiersName);
				modifiers.setAccessible(true);
				modifiers.setInt(field, field.getModifiers() | Modifier.FINAL);
			}
		} catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
	}
	/**
	 * 返回输入的方法重写的父类的方法
	 * @param input
	 * @return 如果{@code input}为{@code null}或非重写父类方法则返回{@code null}
	 */
	public static Method getOverride(Method input){
		if(input == null){
			return null;
		}
		int modifier = input.getModifiers();
		if(Modifier.isStatic(modifier) || Modifier.isPrivate(modifier)){
			return null;
		}
		Class<?> declaringClass = input.getDeclaringClass();
		if(Object.class.equals(declaringClass)){
			return null;
		}		
		Method om = getOverrideObjectMethod(input);
		if(om != null){
			return om;
		}
		
		try {
			Method self = declaringClass.getMethod(input.getName(), input.getParameterTypes());
			if(self.equals(input)){
				return  declaringClass.getSuperclass().getMethod(input.getName(), input.getParameterTypes());		
			}
		} catch (NoSuchMethodException e) {
			// DO NOTHING
		}
		return null;
	}
	/**
	 * 返回输入的方法重写的顶级父类的方法
	 * @param input
	 * @return 如果{@code input}为{@code null}或非重写父类方法则返回{@code null}
	 * @see #getOverride(Method)
	 */
	public static Method getOverrideTop(Method input){
		Method om = input;
		Method sm ;
		while((sm = getOverride(om)) != null){
			om = sm;
		}
		if(Objects.equal(om, input)){
			return null;
		}
		return om;
	}
	/**
	 * 如果输入方法({@code input})为重写方法则返回重写的顶级父类的方法,否则返回{@code input}
	 * @param input
	 * @return 如果{@code input}为{@code null}或非重写父类方法则返回{@code null}
	 * @see #getOverride(Method)
	 */
	public static Method overrideTopOrSelf(Method input){
		Method om = getOverrideTop(input);
		return om == null ? input : om;
	}
	/**
	 * 如果输入的方法为重写{@link Object}类的方法("clone","finalize","hashCode","equals","toString")
	 * 则返回{@link Object}类对应的方法,否则返回{@code null}
	 * @param input
	 * @return {@code input}为{@code null}返回{@code null}
	 */
	public static Method getOverrideObjectMethod(Method input){
		if(input == null){
			return null;
		}
		int modifier = input.getModifiers();
		if(Modifier.isStatic(modifier) || Modifier.isPrivate(modifier)){
			return null;
		}
		String name = input.getName();
		if(!overridableObjectMethods.contains(name)){
			return null;
		}
		try {
			Method om = Object.class.getDeclaredMethod(name, input.getParameterTypes());
			if(om.getReturnType().equals(input.getReturnType())){
				return om;
			}
		} catch (NoSuchMethodException e) {
		}
		return null;
	}
	/**
	 * 
	 * 判断方法是否为重写Object的方法
	 * @param input
	 */
	public static boolean isOverrideObjectMethod(Method input){
		return getOverrideObjectMethod(input) != null;
	}
	/**
	 * 判断{@code input}是否为残存的影子方法<br>
	 * 如果在方法声明的类{@link Method#getDeclaringClass()}中使用根据{@code input}的名字和参数查找方法找到的方法与{@code input}不相同,
	 * 即为因残存的被重写的父类方法,因返回类型不相同而残存
	 * @param input
	 * @return {@code input}为{@code null}返回{@code false} 
	 */
	public static boolean isOverrideShadow(Method input){
		if( input != null){
			Class<?> declaringClass = input.getDeclaringClass();
			if(Object.class.equals(declaringClass)){
				return false;
			}
			try {
				Method self = declaringClass.getMethod(input.getName(), input.getParameterTypes());
				return !self.equals(input);
			} catch (Throwable e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
		return false;
	}
}
